package org.zjvis.datascience.service.graph;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.nimbusds.jose.util.IOUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.zjvis.datascience.common.dto.graph.GraphDTO;
import org.zjvis.datascience.common.dto.graph.GraphFilterPipelineInstanceDTO;
import org.zjvis.datascience.common.dto.graph.GraphInstanceDTO;
import org.zjvis.datascience.common.exception.BaseErrorCode;
import org.zjvis.datascience.common.exception.DataScienceException;
import org.zjvis.datascience.common.graph.constant.GraphAnalysisConstant;
import org.zjvis.datascience.common.graph.constant.GraphConstant;
import org.zjvis.datascience.common.graph.enums.GraphFileFormatEnum;
import org.zjvis.datascience.common.graph.util.GraphUtil;
import org.zjvis.datascience.common.graph.filter.FilterResult;
import org.zjvis.datascience.common.graph.importer.ImporterCSV;
import org.zjvis.datascience.common.graph.importer.ImporterGML;
import org.zjvis.datascience.common.graph.model.DefaultNodeStyle;
import org.zjvis.datascience.common.graph.model.MetricResult;
import org.zjvis.datascience.common.graph.model.MetricResultManager;
import org.zjvis.datascience.common.model.ApiResultCode;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.util.Page;
import org.zjvis.datascience.common.vo.graph.*;
import org.zjvis.datascience.service.MinioService;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Types;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * @description GraphAnalysisService
 * @date 2021-12-29
 */
@Service
public class GraphAnalysisService {
    private static final Logger logger = LoggerFactory.getLogger(GraphAnalysisService.class);

    @Autowired
    private JanusGraphEmbedService janusGraphEmbedService;

    @Autowired
    private GraphService graphService;

    @Autowired
    private MinioService minioService;

    @Autowired
    private GraphInstanceService graphInstanceService;

    @Autowired
    private GraphFilterPipelineInstanceService graphFilterPipelineInstanceService;

    @Autowired
    private GraphFilterPipelineService graphFilterPipelineService;


    private ExecutorService executor = Executors.newFixedThreadPool(10);

    public GraphDTO addGraphByProject(Long projectId) {
        return addGraphByProject(projectId, null);
    }

    public GraphDTO addGraphByProject(Long projectId, String name) {
        List<String> names = graphService.queryNamesByProjectId(projectId);
        String setName = name == null ? GraphAnalysisConstant.GRAPH_DEFAULT_NAME : name;
        String newName = createGraphName(names, setName);
        GraphDTO emptyGraphDTO = new GraphDTO();
        emptyGraphDTO.setUserId(JwtUtil.getCurrentUserId());
        emptyGraphDTO.setProjectId(projectId);
        emptyGraphDTO.setPipelineId(null);
        emptyGraphDTO.setTaskId(null);
        emptyGraphDTO.setName(newName);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("categories", new ArrayList<>());
        jsonObject.put("edges", new ArrayList<>());
        jsonObject.put("nodes", new ArrayList<>());
        jsonObject.put("links", new ArrayList<>());
        JSONObject graphInfo = new JSONObject();
        graphInfo.put("numOfNodes", 0);
        graphInfo.put("numOfLinks", 0);
        graphInfo.put("directed", false);
        jsonObject.put("graphInfo", graphInfo);
        emptyGraphDTO.setDataJson(jsonObject.toJSONString());
        graphService.save(emptyGraphDTO);
        return emptyGraphDTO;
    }

    public String createGraphName(List<String> names, String name) {
        String newName;
        if (names.size() == 0) {
            newName = name;
        } else {
            List<String> defaultName = names.stream()
                    .filter(n -> n.matches("^" + name + "(\\([0-9]+\\))?$"))
                    .collect(Collectors.toList());
            List<Integer> order = defaultName.stream()
                    .map(s -> s.equals(name) ? "0" : s.substring(name.length() + 1, s.length() - 1))
                    .map(Integer::parseInt)
                    .sorted()
                    .collect(Collectors.toList());
            if (order.size() == 0) {
                newName = name;
            } else {
                newName = String.format(name + "(%d)", order.get(order.size() - 1) + 1);
            }
        }
        return newName;
    }

    public Long loadData(Long graphId, Long sourceGraphId, GraphFileFormatEnum format) {
        if (format != GraphFileFormatEnum.GRAPH_BUILD) {
            return null;
        }
        GraphDTO graph = graphService.queryById(graphId);
        GraphInstanceDTO instance = new GraphInstanceDTO(graph);
        JSONObject dataObj = new JSONObject();
        dataObj.put("graph_build_source", sourceGraphId);
        instance.setDataJson(dataObj.toJSONString());
        Long graphInstanceId = graphInstanceService.save(instance);
        GraphParser parser = new GraphParser(format, minioService, graphInstanceService, graphService, janusGraphEmbedService,
                instance, null, null);
        executor.submit(parser);
        return graphInstanceId;

    }

    public Long loadData(Long graphId, String bucketPath, String fileName, GraphFileFormatEnum format) {
        GraphDTO graph = graphService.queryById(graphId);
        GraphInstanceDTO instance = new GraphInstanceDTO(graph);
        Long graphInstanceId = graphInstanceService.save(instance);
        GraphParser parser = new GraphParser(format, minioService, graphInstanceService, graphService, janusGraphEmbedService,
                instance, bucketPath, fileName);
        executor.submit(parser);
        return graphInstanceId;
    }

    public JSONObject queryLoadStatus(Long instanceId) {
        JSONObject obj = new JSONObject();
        GraphInstanceDTO instance = graphInstanceService.queryById(instanceId);
        if (instance == null) {
            return obj;
        }
        String status = instance.getStatus();
        obj.put("status", status);
        if (status.equals("FAIL")) {
            obj.put("log", instance.getLogInfo());
        }
        return obj;
    }

    public boolean checkGraphFile(MultipartFile file) throws IOException {
        InputStream in = file.getInputStream();
        String fileName = file.getOriginalFilename().toLowerCase();
        JSONArray nodeArray = null;
        JSONArray linkArray = null;
        if (fileName.endsWith(".json")) {
            String JSONString = IOUtils.readInputStreamToString(in, StandardCharsets.UTF_8);
            JSONObject obj = JSONArray.parseObject(JSONString);
            nodeArray = obj.getJSONArray("nodes");
            linkArray = obj.getJSONArray("links");
        } else if (fileName.endsWith(".csv")) {
            JSONObject ret = ImporterCSV.parseCSV2JSON(in, null);
            JSONArray headArray = ret.getJSONArray("head");
            List<String> heads = new ArrayList<>();
            for (int i = 0; i < headArray.size() ; i++) {
                JSONObject head = headArray.getJSONObject(i);
                heads.add(head.getString("name"));
            }
            String loadType = GraphParser.checkHeads(heads);
            JSONArray data = ret.getJSONArray("data");
            if (loadType.equals("node")) {
                nodeArray = data;
            } else if (loadType.equals("link")) {
                linkArray = data;
            }
        } else if (fileName.endsWith(".gml")) {
            ImporterGML importerGML = new ImporterGML();
            importerGML.execute(in);
            nodeArray = importerGML.getNodes();
            linkArray = importerGML.getLinks();
        } else {
            throw new DataScienceException(ApiResultCode.GRAPH_LOAD_FORMAT_ERROR, "");
        }

        Map<String, Map<String, Integer>> cIdAttrHelper = new HashMap<>();
        Map<String, Map<String, Integer>> eIdAttrHelper = new HashMap<>();
        Map<Integer, String> nId2Cid = new HashMap<>();
        if (nodeArray != null) {
            nodeArray.forEach( o -> GraphParser.checkNode(o, cIdAttrHelper, nId2Cid));
        }
        if (linkArray != null) {
            linkArray.forEach(o -> GraphParser.checkLink(o, eIdAttrHelper, nId2Cid));
        }
        return true;
    }

    public void batchDeleteNodesAndLinks(Long graphId, List<String> nids, List<String> lids, GraphVO ctx1) {
        GraphDTO graphDTO = graphService.queryById(graphId);
        JSONObject obj = JSONObject.parseObject(graphDTO.getDataJson());
        List<NodeVO> nodes = obj.getJSONArray("nodes").toJavaList(NodeVO.class);
        List<LinkVO> links = obj.getJSONArray("links").toJavaList(LinkVO.class);
        List<NodeVO> removeNodes = nodes.stream().filter(n -> nids.contains(n.getId())).collect(Collectors.toList());
        List<LinkVO> removeLinks = links.stream().filter(l -> lids.contains(l.getId())).collect(Collectors.toList());
        nodes.removeAll(removeNodes);
        links.removeAll(removeLinks);
        obj.put("nodes", nodes);
        obj.put("links", links);
        //更新graphInfo
        obj.getJSONObject("graphInfo").put("numOfNodes", nodes.size());
        obj.getJSONObject("graphInfo").put("numOfLinks", links.size());
        janusGraphEmbedService.batchDropNodesAndLinks(graphId, nids, lids);
        Map<String, String> idMap = obj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        BiMap<String, String> inverse = HashBiMap.create(idMap).inverse();
        for (String id: nids) {
            inverse.remove(id);
        }
        obj.put(GraphConstant.IDMAP_KEY, inverse.inverse());
        graphDTO.setDataJson(obj.toJSONString());
        graphService.update(graphDTO);
        if (ctx1 != null) {
            graphDTO.initActionView(ctx1);
            ctx1.getNodes().addAll(removeNodes);
            ctx1.getLinks().addAll(removeLinks);
        }
    }

    public List<String> simpleQueryFilterNode(GraphDTO graphDTO) {
        GraphFilterPipelineInstanceDTO filterInstance = this.isApplyFilter(graphDTO);
        if (filterInstance != null) {
            JSONObject filterObj = JSONObject.parseObject(filterInstance.getDataJson());
            FilterResult filterResult = filterObj.getObject("filterResult", FilterResult.class);
            Set<String> filterV = filterResult.getFilterV();
            return new ArrayList<>(filterV);
        }
        return null;
    }

    public List<String> simpleQueryFilterLink(GraphDTO graphDTO) {
        GraphFilterPipelineInstanceDTO filterInstance = this.isApplyFilter(graphDTO);
        if (filterInstance != null) {
            JSONObject filterObj = JSONObject.parseObject(filterInstance.getDataJson());
            FilterResult filterResult = filterObj.getObject("filterResult", FilterResult.class);
            Set<String> filterE = filterResult.getFilterE();
            return new ArrayList<>(filterE);
        }
        return null;
    }

    public GraphFilterPipelineInstanceDTO isApplyFilter(GraphDTO graph) {
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        Long filterPipelineInstanceId = graphObj.getLong("filterPipelineInstance");
        if (filterPipelineInstanceId == null) {
            return null;
        }
        return graphFilterPipelineInstanceService.queryById(filterPipelineInstanceId);
    }

    public void applyFilter(GraphDTO graph, Long instanceId) {
        GraphFilterPipelineInstanceDTO instance = graphFilterPipelineInstanceService.queryById(instanceId);
        applyFilter(graph, instance);
    }

    public void applyFilter(GraphDTO graph, GraphFilterPipelineInstanceDTO instance) {
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        List<CategoryVO> categories = graphObj.getJSONArray("categories").toJavaList(CategoryVO.class);
        List<EdgeVO> edges = graphObj.getJSONArray("edges").toJavaList(EdgeVO.class);
        List<NodeVO> nodes = graphObj.getJSONArray("nodes").toJavaList(NodeVO.class);
        List<LinkVO> links = graphObj.getJSONArray("links").toJavaList(LinkVO.class);
        JSONObject filterObj = JSONObject.parseObject(instance.getDataJson());
        FilterResult filterResult = filterObj.getObject("filterResult", FilterResult.class);
        Set<String> filterV = filterResult.getFilterV();
        Set<String> filterE = filterResult.getFilterE();

        List<NodeVO> filterNodes = nodes.stream().filter(n -> filterV.contains(n.getId())).collect(Collectors.toList());
        List<LinkVO> filterLinks = links.stream().filter(l -> filterE.contains(l.getId())).collect(Collectors.toList());

        List<String> cids = filterNodes.stream().map(NodeVO::getCategoryId).distinct().collect(Collectors.toList());
        List<String> eids = filterLinks.stream().map(LinkVO::getEdgeId).distinct().collect(Collectors.toList());

        List<CategoryVO> filterCategories = categories.stream().filter(c -> cids.contains(c.getId())).collect(Collectors.toList());
        List<EdgeVO> filterEdges = edges.stream().filter(e -> eids.contains(e.getId())).collect(Collectors.toList());

        graphObj.put("categories", filterCategories);
        graphObj.put("edges", filterEdges);
        graphObj.put("nodes", filterNodes);
        graphObj.put("links", filterLinks);
        //更新graphInfo
        graphObj.getJSONObject("graphInfo").put("numOfNodes", filterNodes.size());
        graphObj.getJSONObject("graphInfo").put("numOfLinks", filterLinks.size());
        graph.setDataJson(graphObj.toJSONString());
    }

    public JSONObject queryAvailableAttrs(List<CategoryVO> categories, List<String> activateMetric) {
        List<List<CategoryAttrVO>> filterAttrs = categories.stream()
                .map(CategoryVO::getAttrs).collect(Collectors.toList());
        Set<CategoryAttrVO> shareAttrs = new HashSet<>();
        if (filterAttrs.size() > 0) {
            shareAttrs = new HashSet<>(filterAttrs.get(0));
            for (List<CategoryAttrVO> attr: filterAttrs) {
                Set<CategoryAttrVO> attrSet = new HashSet<>(attr);
                shareAttrs.retainAll(attrSet);
            }
        }
        Set<String> shareAttr = shareAttrs.stream().map(CategoryAttrVO::getName).collect(Collectors.toSet());
        Set<String> numAttr = shareAttrs.stream().filter(x->x.getType() == Types.NUMERIC).map(CategoryAttrVO::getName).collect(Collectors.toSet());
        Set<String> stringAttr = shareAttrs.stream().filter(x->x.getType() == Types.VARCHAR).map(CategoryAttrVO::getName).collect(Collectors.toSet());
        if (activateMetric.size() > 0) {
            shareAttr.addAll(activateMetric);
            if (activateMetric.contains(MetricResultManager.ClusterTag)) {
                stringAttr.add(MetricResultManager.ClusterTag);
                activateMetric.remove(MetricResultManager.ClusterTag);
            }
            numAttr.addAll(activateMetric);
        }
        JSONObject obj = new JSONObject();
        List<String> shareAttrSort = new ArrayList<>(shareAttr);
        List<String> numAttrSort = new ArrayList<>(numAttr);
        List<String> stringAttrSort = new ArrayList<>(stringAttr);
        GraphUtil.sortListIgnoreCase(shareAttrSort);
        GraphUtil.sortListIgnoreCase(numAttrSort);
        GraphUtil.sortListIgnoreCase(stringAttrSort);
        obj.put("shareAttr", shareAttrSort);
        obj.put("numAttr", numAttrSort);
        obj.put("stringAttr", stringAttrSort);
        return obj;
    }

    public JSONArray queryAttrGroup(GraphDTO graphDTO, List<String> categoryIds, String attr) {
        DefaultNodeStyle defaultNodeStyle = new DefaultNodeStyle();
        JSONArray ret = new JSONArray();
        List<String> filterNodes = simpleQueryFilterNode(graphDTO);
        JSONObject graphObj = JSONObject.parseObject(graphDTO.getDataJson());
        Map<String, String> idMap = graphObj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        Long graphId = graphDTO.getId();
        Map<Object, Long> countMap = janusGraphEmbedService.queryAttrGroupCountWithLabels(graphId, filterNodes, attr, categoryIds);
        Double countSum = countMap.values().stream().mapToDouble(v -> v).sum();
        Iterator<Map.Entry<Object, Long>> itEntry = countMap.entrySet().iterator();
        Iterator<String> itColor = defaultNodeStyle.getColorLoop().iterator();

        Map<Object, Object> _attrIdMap = janusGraphEmbedService.queryAttrGroupIdWithLabels(graphId, filterNodes, attr, categoryIds);
        Map<Object, List<String>> attrIdMap = _attrIdMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry->((List<Object>)entry.getValue()).stream().map(Object::toString).map(idMap::get).collect(Collectors.toList())));

        while (itEntry.hasNext() && itColor.hasNext()) {
            Map.Entry<Object, Long> entry = itEntry.next();
            String color = itColor.next();
            JSONObject map = new JSONObject();
            map.put("color", color);
            map.put("value", entry.getKey());
            map.put("proportion", entry.getValue() / countSum);
            map.put("count", entry.getValue());
            map.put("nodeIds", attrIdMap.get(entry.getKey()));
            ret.add(map);
        }
        if (itEntry.hasNext()) {
            //color used up
            Long otherSum = 0L;
            List<String> otherIds = new ArrayList<>();
            while (itEntry.hasNext()) {
                Map.Entry<Object, Long> entry = itEntry.next();
                otherSum += entry.getValue();
                otherIds.addAll(attrIdMap.get(entry.getKey()));
            }
            JSONObject map = new JSONObject();
            map.put("color", defaultNodeStyle.getDefaultColor());
            map.put("value", "_other_");
            map.put("proportion", otherSum / countSum);
            map.put("count", otherSum);
            map.put("nodeIds", otherIds);
            ret.add(map);
        }
        return ret;
    }

    public JSONArray queryAttrSort(GraphDTO graphDTO, List<String> categoryIds, String attr) {
        List<String> filterNodes = simpleQueryFilterNode(graphDTO);
        JSONObject graphObj = JSONObject.parseObject(graphDTO.getDataJson());
        Map<String, String> idMap = graphObj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        JSONObject ret = janusGraphEmbedService.queryAttrSortWithLabels(graphDTO.getId(), filterNodes, attr, categoryIds);
        if (ret == null) {
            return new JSONArray();
        }
        Double max = ret.getDouble("max");
        Double min = ret.getDouble("min");
        Double scale = max - min;
        JSONArray result = ret.getJSONArray("result");
        for (int i = 0; i < result.size(); i++) {
            JSONObject jo = result.getJSONObject(i);
            jo.put("id", idMap.get(jo.get("id").toString()));
            if (scale != 0) {
                jo.put("value", (Double.parseDouble(jo.get("value").toString()) - min) / scale);
            } else {
                jo.put("value", Double.parseDouble(jo.get("value").toString()) / max);
            }
        }

        return result;
    }

    public JSONObject executeMetrics(Long graphId, String type) {
        List<String> nids = new ArrayList<>();
        List<String> lids = new ArrayList<>();
        GraphDTO graph = getFilterRemove(graphId, nids, lids);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        Long numOfNodes = 0L;
        Long numOfLinks = 0L;
        Boolean directed = false;
        if (graphObj.getJSONObject("graphInfo") != null) {
            numOfNodes = graphObj.getJSONObject("graphInfo").getLong("numOfNodes") - nids.size();
            numOfLinks = graphObj.getJSONObject("graphInfo").getLong("numOfLinks") - lids.size();
            directed = graphObj.getJSONObject("graphInfo").getBoolean("directed");
        }

        MetricResultManager metricResultManager = graphObj.getObject("metricResultManager", MetricResultManager.class);
        if (metricResultManager == null) {
            metricResultManager = new MetricResultManager();
        }
        JSONObject ret = janusGraphEmbedService.executeMetricsAndReport(graphId, nids, lids, metricResultManager, type, numOfNodes, numOfLinks, directed);
        graphObj.put("metricResultManager", metricResultManager);
        graph.setDataJson(graphObj.toJSONString());
        graphService.update(graph);

        //已经激活的属性直接写入
        Map<String, Boolean> activateMap = metricResultManager.getActivateMap();
        List<String> tags = activateMap.entrySet().stream().filter(Map.Entry::getValue)
                .map(Map.Entry::getKey).collect(Collectors.toList());
        List<String> validTags = metricResultManager.selectTags(type);
        tags.retainAll(validTags);
        List<MetricResult> metrics = metricResultManager.retrieveFromTag(tags);
        if (metrics.size() > 0) {
            MetricResult[] metricResults = metrics.toArray(new MetricResult[0]);
            janusGraphEmbedService.saveMetrics(graphId, metricResults);
        }
        return ret;
    }

    public JSONObject queryMetrics(Long graphId) {
        JSONObject ret = new JSONObject();
        GraphDTO graph = graphService.queryById(graphId);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        Boolean directed = graphObj.getJSONObject("graphInfo").getBoolean("directed");
        MetricResultManager metricResultManager = graphObj.getObject("metricResultManager", MetricResultManager.class);
        if (metricResultManager == null) {
            metricResultManager = new MetricResultManager();
        }

        JSONObject density = new JSONObject();
        if (metricResultManager.getDensity() != null) {
            density.put("value", metricResultManager.getDensity().getValue());
        }

        JSONObject meanDegree = new JSONObject();
        if (metricResultManager.getDegree() != null) {
            meanDegree.put("value", metricResultManager.getDegree().getValue());
            meanDegree.put("detail", groupCountMap(metricResultManager.getDegree().getResult()));
        }

        JSONObject meanWeightedDegree = new JSONObject();
        if (metricResultManager.getWeightedDegree() != null) {
            meanWeightedDegree.put("value", metricResultManager.getWeightedDegree().getValue());
            meanWeightedDegree.put("detail", groupCountMap(metricResultManager.getWeightedDegree().getResult()));
        }

        JSONObject eigenvectorCentrality = new JSONObject();
        if (metricResultManager.getEigenvectorCentrality() != null) {
            eigenvectorCentrality.put("detail", groupCountMap(metricResultManager.getEigenvectorCentrality().getResult()));
        }

        JSONObject betweennessCentrality = new JSONObject();
        if (metricResultManager.getBetweennessCentrality() != null) {
            betweennessCentrality.put("detail", groupCountMap(metricResultManager.getBetweennessCentrality().getResult()));
        }

        JSONObject closenessCentrality = new JSONObject();
        if (metricResultManager.getClosenessCentrality() != null) {
            closenessCentrality.put("detail", groupCountMap(metricResultManager.getClosenessCentrality().getResult()));
        }

        JSONObject eccentricity = new JSONObject();
        if (metricResultManager.getEccentricity() != null) {
            eccentricity.put("detail", groupCountMap(metricResultManager.getEccentricity().getResult()));
        }

        JSONObject diameter = new JSONObject();
        JSONObject avgDist = new JSONObject();
        if (metricResultManager.getDistance() != null) {
            Map valueMap = (Map) metricResultManager.getDistance().getValue();
            diameter.put("value", valueMap.get("Diameter"));
            avgDist.put("value", valueMap.get("AvgDist"));
        }

        JSONObject pageRank = new JSONObject();
        if (metricResultManager.getPageRank() != null) {
            pageRank.put("detail", groupCountMap(metricResultManager.getPageRank().getResult()));
        }

        JSONObject cluster = new JSONObject();
        if (metricResultManager.getCluster() != null) {
            JSONObject value = (JSONObject) metricResultManager.getCluster().getValue();
            if (value != null) {
                cluster.putAll(value);
            }
        }

        JSONObject connectedComponents = new JSONObject();
        JSONObject wcc = new JSONObject();
        if (metricResultManager.getWeaklyconnectedcomponents() != null) {
            wcc.put("detail", groupCountSccMap(metricResultManager.getWeaklyconnectedcomponents().getResult()));
            wcc.put("value", metricResultManager.getWeaklyconnectedcomponents().getValue());
        }

        JSONObject scc = new JSONObject();
        if (metricResultManager.getStronglyconnectedcomponents() != null) {
            scc.put("detail", groupCountSccMap(metricResultManager.getStronglyconnectedcomponents().getResult()));
            scc.put("value", metricResultManager.getStronglyconnectedcomponents().getValue());
        }

        JSONObject cc = new JSONObject();
        if (metricResultManager.getConnectedcomponents() != null) {
            cc.put("detail", groupCountSccMap(metricResultManager.getConnectedcomponents().getResult()));
            cc.put("value", metricResultManager.getConnectedcomponents().getValue());
        }

        JSONObject clusteringCoefficient = new JSONObject();
        if (metricResultManager.getClusteringCoefficient() != null) {
            clusteringCoefficient.put("detail", groupCountMap(metricResultManager.getClusteringCoefficient().getResult()));
            clusteringCoefficient.put("value", metricResultManager.getClusteringCoefficient().getValue());
        }

        JSONObject triangles = new JSONObject();
        if (metricResultManager.getTriangles() != null) {
            triangles.put("value", metricResultManager.getTriangles().getValue());
        }

        if (directed == null) {
            connectedComponents.put("ConnectedComponents", cc);
        } else if (directed) {
            connectedComponents.put("StronglyConnectedComponents", scc);
            connectedComponents.put("WeaklyConnectedComponents", wcc);
        } else {
            connectedComponents.put("ConnectedComponents", cc);
        }

        ret.put("MeanDegree", meanDegree);
        ret.put("MeanWeightedDegree", meanWeightedDegree);
        ret.put("EigenvectorCentrality", eigenvectorCentrality);

        ret.put("BetweennessCentrality", betweennessCentrality);
        ret.put("ClosenessCentrality", closenessCentrality);
        ret.put("Eccentricity", eccentricity);

        ret.put("ConnectedComponents", connectedComponents);

        ret.put("ClusteringCoefficient", clusteringCoefficient);
        ret.put("Triangles", triangles);

        ret.put("Density", density);
        ret.put("Diameter", diameter);
        ret.put("AvgDist", avgDist);

        ret.put("PageRank", pageRank);
        ret.put("Cluster", cluster);
        return ret;
    }

    public GraphDTO getFilterRemove(Long graphId, List<String> nids, List<String> lids) {
        GraphDTO graph = graphService.queryById(graphId);
        GraphFilterPipelineInstanceDTO filterInstance = this.isApplyFilter(graph);
        if (filterInstance == null) {
            return graph;
        }
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        List<NodeVO> nodes = graphObj.getJSONArray("nodes").toJavaList(NodeVO.class);
        List<LinkVO> links = graphObj.getJSONArray("links").toJavaList(LinkVO.class);
        JSONObject filterObj = JSONObject.parseObject(filterInstance.getDataJson());
        FilterResult filterResult = filterObj.getObject("filterResult", FilterResult.class);
        Set<String> filterV = filterResult.getFilterV();
        Set<String> filterE = filterResult.getFilterE();
        List<NodeVO> removeNodes = nodes.stream().filter(n -> !filterV.contains(n.getId())).collect(Collectors.toList());
        List<LinkVO> removeLinks = links.stream().filter(l -> !filterE.contains(l.getId())).collect(Collectors.toList());
        nids.addAll(removeNodes.stream().map(NodeVO::getId).collect(Collectors.toList()));
        lids.addAll(removeLinks.stream().map(LinkVO::getId).collect(Collectors.toList()));
        return graph;
    }

    public JSONObject queryPageRank(Long graphId) {
        List<String> nids = new ArrayList<>();
        List<String> lids = new ArrayList<>();
        GraphDTO graph = getFilterRemove(graphId, nids, lids);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        MetricResultManager metricResultManager = graphObj.getObject("metricResultManager", MetricResultManager.class);
        if (metricResultManager == null) {
            metricResultManager = new MetricResultManager();
        }
        JSONObject ret = janusGraphEmbedService.pageRank(graphId, nids, lids, metricResultManager);
        graphObj.put("metricResultManager", metricResultManager);
        graph.setDataJson(graphObj.toJSONString());
        graphService.update(graph);

        //已经激活的属性直接写入
        Map<String, Boolean> activateMap = metricResultManager.getActivateMap();
        if (activateMap.get(MetricResultManager.PageRankTag)) {
            MetricResult metricResult = metricResultManager.getPageRank();
            janusGraphEmbedService.saveMetrics(graphId, metricResult);
        }
        return ret;
    }

    public JSONObject queryCluster(Long graphId) {
        List<String> nids = new ArrayList<>();
        List<String> lids = new ArrayList<>();
        GraphDTO graph = getFilterRemove(graphId, nids, lids);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        MetricResultManager metricResultManager = graphObj.getObject("metricResultManager", MetricResultManager.class);
        if (metricResultManager == null) {
            metricResultManager = new MetricResultManager();
        }
        Map<String, String> idMap = graphObj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        JSONObject ret = janusGraphEmbedService.cluster(graphId, nids, lids, null, metricResultManager, idMap);
        graphObj.put("metricResultManager", metricResultManager);
        graph.setDataJson(graphObj.toJSONString());
        graphService.update(graph);

        Map<String, Boolean> activateMap = metricResultManager.getActivateMap();
        if (activateMap.get(MetricResultManager.ClusterTag)) {
            MetricResult metricResult = metricResultManager.getCluster();
            janusGraphEmbedService.saveMetrics(graphId, metricResult);
        }
        return ret;
    }

    public Boolean activateMetric(Long graphId, String metricTag, Boolean activate) {
        GraphDTO graph = graphService.queryById(graphId);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        Boolean directed = graphObj.getJSONObject("graphInfo").getBoolean("directed");
        MetricResultManager metricResultManager = graphObj.getObject("metricResultManager", MetricResultManager.class);
        if (metricResultManager == null) {
            return false;
        }
        Map<String, Boolean> activateMap = metricResultManager.getActivateMap();
        List<String> metricKeys = new ArrayList<>();
        MetricResult[] metricResults = new MetricResult[1];
        switch (metricTag) {
            case "MeanDegree":
                metricKeys.add(MetricResultManager.DegreeTag);
                metricResults[0] = metricResultManager.getDegree();
                break;
            case "MeanWeightedDegree":
                metricKeys.add(MetricResultManager.WeightedDegreeTag);
                metricResults[0] = metricResultManager.getWeightedDegree();
                break;
            case "Diameter":
            case "AvgDist":
                metricKeys.add(MetricResultManager.BetweennessCentralityTag);
                metricKeys.add(MetricResultManager.ClosenessCentralityTag);
                metricKeys.add(MetricResultManager.EccentricityTag);
                metricResults = new MetricResult[3];
                metricResults[0] = metricResultManager.getBetweennessCentrality();
                metricResults[1] = metricResultManager.getClosenessCentrality();
                metricResults[2] = metricResultManager.getEccentricity();
                break;
            case "EigenvectorCentrality":
                metricKeys.add(MetricResultManager.EigenvectorCentralityTag);
                metricResults[0] = metricResultManager.getEigenvectorCentrality();
                break;
            case "PageRankScore":
                metricKeys.add(MetricResultManager.PageRankTag);
                metricResults[0] = metricResultManager.getPageRank();
                break;
            case "ModularityClass":
                metricKeys.add(MetricResultManager.ClusterTag);
                metricResults[0] = metricResultManager.getCluster();
                break;

            case "ConnectedComponents":
                if (directed) {
                    metricKeys.add(MetricResultManager.StronglyConnectedComponentsTag);
                    metricKeys.add(MetricResultManager.WeaklyConnectedComponentsTag);
                    metricResults = new MetricResult[2];
                    metricResults[0] = metricResultManager.getStronglyconnectedcomponents();
                    metricResults[1] = metricResultManager.getWeaklyconnectedcomponents();
                } else {
                    metricKeys.add(MetricResultManager.ConnectedComponentsTag);
                    metricResults[0] = metricResultManager.getConnectedcomponents();
                }
                break;
            case "ClusteringCoefficient":
                metricKeys.add(MetricResultManager.ClusteringCoefficientTag);
                metricKeys.add(MetricResultManager.TrianglesTag);
                metricResults = new MetricResult[2];
                metricResults[0] = metricResultManager.getClusteringCoefficient();
                metricResults[1] = metricResultManager.getTriangles();
                break;
            default:
                return false;
        }
        for (MetricResult metricResult: metricResults) {
            if (metricResult == null) {
                return false;
            }
        }

        for (String metricKey: metricKeys) {
            activateMap.put(metricKey, activate);
        }

        graphObj.put("metricResultManager", metricResultManager);
        graph.setDataJson(graphObj.toJSONString());
        graphService.update(graph);
        if (activate) {
            janusGraphEmbedService.saveMetrics(graphId, metricResults);
        }
        return true;
    }

    public List<String> queryActivateMetricAttr(GraphDTO graph) {
        List<String> metrics = new ArrayList<>();
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        MetricResultManager metricResultManager = graphObj.getObject("metricResultManager", MetricResultManager.class);
        if (metricResultManager == null) {
            return metrics;
        }
        Map<String, Boolean> activateMap = metricResultManager.getActivateMap();
        for (String key: activateMap.keySet()) {
            if (activateMap.get(key)) {
                metrics.add(key);
            }
        }
        return metrics;
    }

    public JSONObject queryNodeDetail(Long graphId, String categoryId, Integer curPage, Integer pageSize) {
        JSONObject ret = new JSONObject();
        GraphDTO graph = graphService.queryById(graphId);
        List<String> filtreNodes = simpleQueryFilterNode(graph);
        Long count = janusGraphEmbedService.queryVertexDetailCount(graphId, categoryId, filtreNodes);
        Integer skip = (curPage - 1) * pageSize;
        Integer limit = pageSize;
        Page page = new Page();
        page.setCurPage(curPage);
        page.setPageSize(pageSize);
        page.setTotalElementsAndPage(count.intValue());

        JSONObject obj = JSONObject.parseObject(graph.getDataJson());
        List<CategoryVO> categories = obj.getJSONArray("categories").toJavaList(CategoryVO.class);
        Optional <CategoryVO> opt = categories.stream().filter(c -> c.getId().equals(categoryId)).findFirst();
        if (!opt.isPresent()) {
            return ret;
        }
        CategoryVO category = opt.get();
        List<String> colName = category.getAttrs().stream()
                .map(CategoryAttrVO::getName)
                .collect(Collectors.toList());
        List<String> metircs = queryActivateMetricAttr(graph);
        colName.addAll(metircs);
        List<Map<Object, Object>> kvList = janusGraphEmbedService.queryVertexDetail(graphId, categoryId, filtreNodes, skip, limit);
        JSONArray data = new JSONArray();
        String idKey = colName.contains("id") ? "id(primary)" : "id";
        kvList.stream().forEach(map -> {
            Map<String, Object> row = new HashMap<>();
            row.put(idKey, ((List) map.get(JanusGraphEmbedService.orderPropKeyName)).get(0));
            for (String head: colName) {
                List<Object> val = (List) map.get(GraphUtil.aliasPropertyKey(head, graphId));
                row.put(head, val == null || val.isEmpty() ? null : val.get(0));
            }
            data.add(row);
        });
        List<String> head = new ArrayList<>();
        head.add(idKey);
        head.addAll(colName);
        ret.put("page", page);
        ret.put("head", head);
        ret.put("data", data);
        return ret;
    }

    public JSONObject queryLinkDetail(Long graphId, String edgeId, Integer curPage, Integer pageSize) {
        JSONObject ret = new JSONObject();
        GraphDTO graph = graphService.queryById(graphId);
        List<String> filterLinks = simpleQueryFilterLink(graph);
        Long count = janusGraphEmbedService.queryEdgeDetailCount(graphId, edgeId, filterLinks);
        Integer skip = (curPage - 1) * pageSize;
        Integer limit = pageSize;
        Page page = new Page();
        page.setCurPage(curPage);
        page.setPageSize(pageSize);
        page.setTotalElementsAndPage(count.intValue());

        JSONObject obj = JSONObject.parseObject(graph.getDataJson());
        List<EdgeVO> edges = obj.getJSONArray("edges").toJavaList(EdgeVO.class);
        Optional<EdgeVO> opt = edges.stream().filter(e -> e.getId().equals(edgeId)).findFirst();
        if (!opt.isPresent()) {
            return ret;
        }
        EdgeVO edge = opt.get();
        List<String> colName = edge.getAttrs().stream()
                .map(CategoryAttrVO::getName)
                .collect(Collectors.toList());
        List<Map<String, Object>> resultMap = janusGraphEmbedService.queryEdgeDetail(graphId, edgeId, filterLinks, skip, limit);
        JSONArray data = new JSONArray();
        String idKey = colName.contains("id") ? "id(primary)" : "id";
        resultMap.stream().forEach(map -> {
            Map<String, Object> row = new HashMap<>();
            Map kvMap = (Map)map.get("kv");
            row.put(idKey, kvMap.get(JanusGraphEmbedService.orderPropKeyName));
            row.put("source", map.get("src"));
            row.put("target", map.get("tar"));
            row.put("directed", kvMap.get(JanusGraphEmbedService.edgeDirectedPropKeyName));
            row.put("weight", kvMap.get(JanusGraphEmbedService.edgeWeightPropKeyName));
            for (String head: colName) {
                Object val = kvMap.get(GraphUtil.aliasPropertyKey(head, graphId));
                row.put(head, val);
            }
            data.add(row);
        });
        List<String> head = new ArrayList<>();
        head.add(idKey);
        head.add("source");
        head.add("target");
        head.add("directed");
        head.add("weight");
        head.addAll(colName);
        ret.put("page", page);
        ret.put("head", head);
        ret.put("data", data);
        return ret;
    }

    public JSONObject search(Long graphId, List<String> categoryIds, Integer mode, String predicate) {
        JSONObject ret = new JSONObject();
        List<String> nids = new ArrayList<>();
        List<String> lids = new ArrayList<>();
        GraphDTO graph = getFilterRemove(graphId, nids, lids);
        Set<String> _attrs = new HashSet<>();
        JSONObject obj = JSONObject.parseObject(graph.getDataJson());
        List<CategoryVO> _categories = obj.getJSONArray("categories").toJavaList(CategoryVO.class);
        List<CategoryVO> categories = _categories.stream().filter(c -> categoryIds.contains(c.getId())).collect(Collectors.toList());
        if (mode == 1) {
            //主属性
            for (CategoryVO category: categories) {
                if (category.getKeyAttr() != null) {
                    _attrs.add(
                            category.getKeyAttr().getName()
                    );
                }

            }
        } else if (mode == 2) {
            //所有属性
            for (CategoryVO category: categories) {
                if (category.getKeyAttr() != null) {
                    _attrs.add(
                            category.getKeyAttr().getName()
                    );
                }
                _attrs.addAll(
                        category.getAttrs().stream().map(CategoryAttrVO::getName).collect(Collectors.toList())
                );
            }
        }
        List<String> attrs = new ArrayList<>(_attrs);
        attrs.addAll(queryActivateMetricAttr(graph));
        if (attrs.size() > 0) {
//            ret = janusGraphEmbedService.search4LabelAndAttr(graphId, categoryIds, attrs, predicate, nids, lids);
            ret = search0(graph, categoryIds, mode, predicate, nids, lids);
        }
        return ret;
    }

    public JSONObject search0(GraphDTO graph, List<String> categoryIds, Integer mode, String predicate, List<String> removeNids, List<String> removeLids) {
        List<String> target = new ArrayList<>();
        JSONObject obj = JSONObject.parseObject(graph.getDataJson());
        List<NodeVO> nodes = obj.getJSONArray("nodes").toJavaList(NodeVO.class);
        nodes.stream().forEach(node->{
            if (categoryIds.contains(node.getCategoryId()) && !removeNids.contains(node.getId())) {
                String vals;
                if (mode == 1) {
                    vals = node.getAttrs().get(0).toString();
                } else {
                    vals = node.getAttrs().stream().map(a->a.getValue().toString()).collect(Collectors.joining(""));
                }
                if (vals.contains(predicate)) {
                    target.add(node.getId());
                }
            }
        });
        JSONObject ret = new JSONObject();
        ret.put("main", target);
        return ret;
    }

    public List<List<String>> queryPath(Long graphId, String srcId, String tarId, String type, Integer maxStep) {
        List<List<String>> result = new ArrayList<>();
        List<Path> paths;
        List<String> nids = new ArrayList<>();
        List<String> lids = new ArrayList<>();
        GraphDTO graph = getFilterRemove(graphId, nids, lids);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        //get id=integer
        Map<String, String> idMap = graphObj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        if (idMap == null) {
            throw new DataScienceException(BaseErrorCode.GRAPH_DEV_TIP);
        }
        boolean needRecover = janusGraphEmbedService.dropFilterMask(graphId, nids, lids);
        if (type.equals("all")){
            paths = janusGraphEmbedService.allPathBetween2Vertex(graphId, srcId, tarId, maxStep);
            result = paths.stream().map(path->path.objects().stream().map(Object::toString).collect(Collectors.toList())).collect(Collectors.toList());
        }
        else if (type.equals("shortest")) {
            BiMap<String, String> inverse = HashBiMap.create(idMap).inverse();
            List<List<Object>> idResult = janusGraphEmbedService.shortestPathDijk(graphId, Long.parseLong(inverse.get(srcId)),Long.parseLong(inverse.get(tarId)));
            result = idResult.stream().map(path->path.stream().map(Object::toString).map(idMap::get).collect(Collectors.toList())).collect(Collectors.toList());
        }
        else {
            return result;
        }
        if (needRecover) {
            janusGraphEmbedService.rollback();
        }
        return result;
    }

    public static <T, P> List<Map<String, Object>> groupCountSccMap(Map<T, P> map) {
        List<Map<String, Object>> groupCountList = groupCountMap(map);
        return groupCountMap(groupCountList.stream().collect(Collectors.toMap(m->m.get("value"), m->m.get("count"))),
                "size(number of nodes)", "count");
    }

    public static <T, P> List<Map<String, Object>> groupCountMap(Map<T, P> map) {
        return groupCountMap(map, "value", "count");
    }

    public static <T, P> List<Map<String, Object>> groupCountMap(Map<T, P> map, String xLabel, String yLabel) {
        Map<P, Map<String, Object>> result = new HashMap<>();
        for (P val: map.values()) {
            Map<String, Object> v = result.get(val);
            if (v == null) {
                Map<String, Object> cvMap = new HashMap<>();
                cvMap.put(xLabel, val);
                cvMap.put(yLabel, 0L);
                result.put(val, cvMap);
            }
            v = result.get(val);
            v.put(yLabel, ((Long) v.get(yLabel)) + 1);
        }
        return new ArrayList<>(result.values());
    }

    public void updateFilterInstance(Long graphId, Long filterInstanceId) {
        GraphDTO graph = graphService.queryById(graphId);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        graphObj.put("filterPipelineInstance", filterInstanceId);
        graph.setDataJson(graphObj.toJSONString());
        graphService.update(graph);
    }

    public Map<String, Double> queryHeatMap(Long graphId, String srcId) {
        List<String> nids = new ArrayList<>();
        List<String> lids = new ArrayList<>();
        GraphDTO graph = getFilterRemove(graphId, nids, lids);
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        Map<String, String> idMap = graphObj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        if (idMap == null) {
            throw new DataScienceException(BaseErrorCode.GRAPH_DEV_TIP);
        }
        boolean needRecover = janusGraphEmbedService.dropFilterMask(graphId, nids, lids);
        String source = HashBiMap.create(idMap).inverse().get(srcId);
        Map<Object, Double> distanceMap = janusGraphEmbedService.queryDistanceMapFromOne(graphId, Long.parseLong(source));
        Map<String, Double> ret = distanceMap.entrySet().stream().collect(Collectors.toMap(entry->idMap.get(entry.getKey().toString()), Map.Entry::getValue));
        if (needRecover) {
            janusGraphEmbedService.rollback();
        }
        return ret;
    }

    public void queryIdMap(GraphDTO graph) {
        //兼容旧数据生成idmap
        JSONObject graphObj = JSONObject.parseObject(graph.getDataJson());
        Map<String, String> idMap = graphObj.getObject(GraphConstant.IDMAP_KEY, Map.class);
        if (idMap == null) {
            graphObj.put(GraphConstant.IDMAP_KEY, janusGraphEmbedService.queryIdMap(graph.getId()));
            graph.setDataJson(graphObj.toJSONString());
            graphService.update(graph);
        }
    }


}
